package com.hbk.base.component;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hbk.base.util.JasyptUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class EnvPostProcessor implements EnvironmentPostProcessor, SmartApplicationListener {

    private static final String APP_HOME = "app.home";
    private static final String CONFIG_FILE_NAME = "config.json";
    private static final String ENC_PREFIX = "{enc}";
    private static String CONFIG_FULL_PATH;
    private static ExternalConfig EXTERNAL_CONFIG;

    private static final DeferredLog logger = new DeferredLog();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {

        String appHome = System.getProperty(APP_HOME);
        if(StringUtils.isEmpty(appHome)) {
            logger.error("app.home can not empty");
            throw new RuntimeException("app.home can not empty");
        }
        if(!appHome.endsWith("/") && !appHome.endsWith("\\")){
            appHome = appHome + "/";
        }


        StringBuilder builder = new StringBuilder();
        builder.append(appHome);
        builder.append(CONFIG_FILE_NAME);
        CONFIG_FULL_PATH = builder.toString();
        FileInputStream fileInputStream = null;
        ExternalConfig externalConfig = null;
        try {
            fileInputStream = new FileInputStream(CONFIG_FULL_PATH);
            String content = IOUtils.toString(fileInputStream, "UTF-8");
            externalConfig = JSONObject.parseObject(content, ExternalConfig.class);

            // 创建文件上传目录
            String uploadPath = appHome + "upload";
            File uploadDirFile = new File(uploadPath);
            if(!uploadDirFile.exists()){
                uploadDirFile.mkdirs();
            }
            externalConfig.getEditor().setImgDir(uploadPath);
        } catch (Exception e){
            logger.error("config file parse error");
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
        if(externalConfig==null){
            logger.error("config file parse error");
        }else{
            logger.info("config file parse success");
            getEncodeObj(externalConfig);
            getDecodeObj(externalConfig);
        }
    }

    private void getEncodeObj(ExternalConfig config){
        String configString = JSONObject.toJSONString(config);
        ExternalConfig obj = JSONObject.parseObject(configString, ExternalConfig.class);
        String mysqlUsername = obj.getMysql().getUsername();
        String mysqlPassword = obj.getMysql().getPassword();
        String redisPassword = obj.getRedis().getPassword();

        boolean needWrite = false;
        if(!mysqlUsername.startsWith(ENC_PREFIX)){
            obj.getMysql().setUsername(encodeString(mysqlUsername));
            needWrite = true;
        }
        if(!mysqlPassword.startsWith(ENC_PREFIX)){
            obj.getMysql().setPassword(encodeString(mysqlPassword));
            needWrite = true;
        }
        if(!redisPassword.startsWith(ENC_PREFIX)){
            obj.getRedis().setPassword(encodeString(redisPassword));
            needWrite = true;
        }
        // 加密后的数据写到外部配置文件
        if(needWrite){
            logger.info("need write enc to config file");
            String pretty = JSONObject.toJSONString(obj,
                    SerializerFeature.PrettyFormat,
                    SerializerFeature.WriteMapNullValue,
                    SerializerFeature.WriteDateUseDateFormat);
            try {
                FileUtils.writeStringToFile(new File(CONFIG_FULL_PATH), pretty,
                        String.valueOf(StandardCharsets.UTF_8), false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void getDecodeObj(ExternalConfig config){

        String configString = JSONObject.toJSONString(config);
        ExternalConfig obj = JSONObject.parseObject(configString, ExternalConfig.class);

        String mysqlUsername = obj.getMysql().getUsername();
        String mysqlPassword = obj.getMysql().getPassword();
        String redisPassword = obj.getRedis().getPassword();

        if(mysqlUsername.startsWith(ENC_PREFIX)){
            obj.getMysql().setUsername(decodeString(mysqlUsername));
        }
        if(mysqlPassword.startsWith(ENC_PREFIX)){
            obj.getMysql().setPassword(decodeString(mysqlPassword));
        }
        if(redisPassword.startsWith(ENC_PREFIX)){
            obj.getRedis().setPassword(decodeString(redisPassword));
        }
        EXTERNAL_CONFIG = obj;
    }

    public static ExternalConfig getExternalConfig(){
        return EXTERNAL_CONFIG;
    }

    private String decodeString(String text){
        String substring = text.substring(5);
        return JasyptUtil.decode(substring);
    }

    private String encodeString(String text){
        String encode = JasyptUtil.encode(text);
        return ENC_PREFIX + encode;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof ApplicationPreparedEvent) {
            logger.replayTo(EnvPostProcessor.class);
        }
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }
}
